package lock.redisRenewDistLock;

import lombok.Data;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * redis 续期分布式锁
 *
 * @Author: ShenWei
 * @Date: 2024/06/27/8:42
 */
@Component
@Data
public class RedisDistLockWithDog implements Lock {

    // 记录本系统当前获取锁的线程
    private Thread ownerThread;
    // 设置锁自动释放时长
    private static final int LOCK_TIME = 1 * 1000;
    // 设置锁名称
    private static final String LOCK_NAME = "lock";
    // 保存每个线程的uuid - 密码
    private ThreadLocal<String> lockerId = new ThreadLocal<>();
    // 共享的看门狗线程
    private Thread dogThread;
    // 创建一个延迟队列 避免无用的轮询,减少看门狗轮询次数
    private static DelayQueue<ItemVO<LockItem>> delayQueue =  new DelayQueue<>();
    // redis续锁Lua脚本
    private static final String DELAY_LOCK_LUA =
            "if redis.call('get', KEYS[1]) == ARGV[1]\n" +
            "    then\n" +
            "        return redis.call('pexpire', KEYS[1],ARGV[2])\n" +
            "    else \n" +
            "        return 0 \n" +
            "    end";
    private static final String RELEASE_LOCK_LUA =
            "if redis.call('get', KEYS[1]) == ARGV[1] \n" +
            "    then\n" +
            "        return redis.call('del', KEYS[1])\n" +
            "    else\n" +
            "        return 0\n" +
            "    end";

    // 注入JedisPool
    @Resource
    private JedisPool jedisPool = new JedisPool();

    public void setOwnerThread(Thread ownerThread) {
        this.ownerThread = ownerThread;
    }

    @Override
    public void lock() {
        while (!tryLock()) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public boolean tryLock() {
        Thread thread = Thread.currentThread();
        // 校验本线程是否已持有锁
        if (thread == ownerThread) {
            return true;
        } else if (null != ownerThread) {
            // 如果是别的线程持有锁
            return false;
        }
        // 如果没有任何线程持有锁 - 开始加锁
        Jedis jedis = null;
        try {
            // 获取jedis实例
            jedis = jedisPool.getResource();
            // 获取一个唯一校验密码
            String uuid = UUID.randomUUID().toString();
            // 设置redis加锁参数
            SetParams params = new SetParams();
            params.nx();
            params.px(LOCK_TIME);
            // 同步代码块实现抢锁
            synchronized (this) {
                // 再次判断锁是否被占用 & 在redis中加上分布式锁
                if (null == ownerThread && "OK".equals(jedis.set(LOCK_NAME, uuid, params))) {
                    // 如果加锁成功
                    // 记录当前线程的uuid
                    lockerId.set(uuid);
                    // 记录获取锁的线程实例
                    setOwnerThread(thread);
                    // 启动看门狗线程
                    if (null == dogThread) {
                        dogThread = new Thread(null, new DogTask(), "dogTask");
                        dogThread.setDaemon(true);  // 设置为守护线程
                        dogThread.start();          // 启动线程，执行其run方法
                    }
                    // 向延迟阻塞队列中加入元素，让看门狗在锁过期时间之前一点的时间去做锁的续期
                    delayQueue.add(new ItemVO<>(Integer.valueOf(LOCK_TIME), new LockItem(LOCK_NAME, uuid)));
                    System.out.println("已获取锁---");
                    return true;
                } else {
                    System.out.println("无法获取锁---");
                    return false;
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("分布式锁加锁失败", e);
        } finally {
            if (null != jedis) jedis.close();
        }
    }

    // 看门狗线程实体类
    private class DogTask implements Runnable {

        @Override
        public void run() {
            System.out.println("看门狗线程已启动...");
            // 创建一个循环,只要守护线程存活就一直执行
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Jedis jedis = null;
                    // 只有元素快到期了才能take到 0.9s
                    LockItem data = delayQueue.take().getData();
                    try {
                        jedis = jedisPool.getResource();
                        Long result = (Long) jedis.eval(DELAY_LOCK_LUA,
                                Arrays.asList(data.getKey()),
                                Arrays.asList(data.getValue(), String.valueOf(LOCK_TIME)));
                        if (result == 0L) {
                            System.out.println("Redis上的锁已释放，无需续期!");
                        } else {
                            delayQueue.add(new ItemVO<>(Integer.valueOf(LOCK_TIME), new LockItem(data.getKey(), data.getValue())));
                            System.out.println("Redis上的锁已续期:" + LOCK_TIME);
                        }
                    } catch (Exception e) {
                        throw new RuntimeException("锁续期失败!", e);
                    } finally {
                        if (jedis != null) jedis.close();
                    }
                } catch (Exception e) {
                    System.out.println("看门狗线程被中断!");
                    break;
                }
            }
            System.out.println("看门狗线程准备关闭...");
        }
    }

    /**
     * 解锁
     */
    @Override
    public void unlock() {
        if (ownerThread != Thread.currentThread()) {
            throw new RuntimeException("试图释放无所有权的锁!");
        }
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Long result = (Long) jedis.eval(RELEASE_LOCK_LUA, Arrays.asList(LOCK_NAME), Arrays.asList(lockerId.get()));
            if (result != 0L) {
                System.out.println("redis上的锁已释放");
            } else {
                System.out.println("redis上的锁释放失败");
            }
        } catch (Exception e) {
            throw new RuntimeException("锁释放失败!");
        } finally {
            if (null != jedis) jedis.close();
            lockerId.remove();
            setOwnerThread(null);
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        throw new UnsupportedOperationException("不支持可中断获取锁！");
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        throw new UnsupportedOperationException("不支持等待尝试获取锁！");
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
